﻿using ThomasGIS.Coordinates;
using ThomasGIS.Geometries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThomasGIS.Projections
{
    public class MecatorProjection : IProjection
    {
        public double FalseEasting { get; set; } = 0;
        public double FalseNorthing { get; set; } = 0;
        public double CentralMeridian { get; set; } = 0;
        public double StandardParallel { get; set; } = 0;
        public string Name { get; set; } = "Mecator";
        public string Type { get; set; } = "Mecator";

        public string GetProjectionName()
        {
            return Name;
        }

        public string GetProjectionType()
        {
            return Type;
        }

        public MecatorProjection(string name, double B0, double L0, double xOffset = 0, double yOffset = 0)
        {
            this.Name = name;
            this.Type = "Mecator";
            this.CentralMeridian = L0;
            this.StandardParallel = B0;
            this.FalseEasting = xOffset;
            this.FalseNorthing = yOffset;
        }

        public MecatorProjection(CoordinateBase coordinateBase)
        {
            if (coordinateBase.GetCoordinateType() == CoordinateType.Projected)
            {
                ProjectedCoordinate trueCoordinate = coordinateBase as ProjectedCoordinate;
                this.Name = trueCoordinate.Name;
                this.Type = trueCoordinate.Projection;

                if (trueCoordinate.Parameters.ContainsKey("Central_Meridian"))
                {
                    this.CentralMeridian = trueCoordinate.Parameters["Central_Meridian"];
                }

                if (trueCoordinate.Parameters.ContainsKey("Standard_Parallel_1"))
                {
                    this.CentralMeridian = trueCoordinate.Parameters["Standard_Parallel_1"];
                }

                if (trueCoordinate.Parameters.ContainsKey("False_Northing"))
                {
                    this.FalseNorthing = trueCoordinate.Parameters["False_Northing"];
                }

                if (trueCoordinate.Parameters.ContainsKey("False_Easting"))
                {
                    this.FalseNorthing = trueCoordinate.Parameters["False_Easting"];
                }
            }
        }

        public Dictionary<string, double> GetWrittenParameters()
        {
            Dictionary<string, double> parameters = new Dictionary<string, double>();
            parameters.Add("Central_Meridian", this.CentralMeridian);
            parameters.Add("Standard_Parallel_1", this.StandardParallel);
            parameters.Add("False_Northing", this.FalseNorthing);
            parameters.Add("False_Easting", this.FalseEasting);
            return parameters;
        }

        public void Toward(Point point, double a, double flatten)
        {
            // 短半轴
            double b = a - (1.0 / flatten * a);
            // 第一偏心率的平方
            double e = Math.Pow(Math.Pow(a * a - b * b, 0.5) / a, 2);
            // 第二偏心率的平方
            double ep = Math.Pow(Math.Pow(a * a - b * b, 0.5) / b, 2);

            double K = ((a * a / b) / Math.Sqrt(1 + Math.Pow(ep, 2) * Math.Pow(Math.Cos(this.StandardParallel / 180.0 * Math.PI), 2))) * Math.Cos(this.StandardParallel / 180.0 * Math.PI);
            point.X = K * (point.X / 180.0 * Math.PI - this.CentralMeridian / 180.0 * Math.PI) + this.FalseEasting;
            point.Y = K * Math.Log((Math.Tan(Math.PI / 4.0 + point.Y / 180.0 * Math.PI / 2.0)) * Math.Pow(((1 - e * Math.Sin(point.Y / 180.0 * Math.PI)) / (1 + e * Math.Sin(point.Y / 180.0 * Math.PI))), e / 2)) + FalseNorthing;
        }

        public void Backward(Point point, double a, double flatten)
        {
            // 短半轴
            double b = a - (1.0 / flatten * a);
            // 第一偏心率的平方
            double e = Math.Pow(Math.Pow(a * a - b * b, 0.5) / a, 2);
            // 第二偏心率的平方
            double ep = Math.Pow(Math.Pow(a * a - b * b, 0.5) / b, 2);

            point.X -= this.FalseEasting;
            point.Y -= this.FalseNorthing;

            double K = ((a * a / b) / Math.Sqrt(1 + Math.Pow(ep, 2) * Math.Pow(Math.Cos(this.StandardParallel / 180.0 * Math.PI), 2))) * Math.Cos(this.StandardParallel / 180.0 * Math.PI);
            point.X = ((point.X / K) + (this.CentralMeridian / 180.0 * Math.PI)) * 180.0 / Math.PI;
            point.Y = (2 * Math.Tanh(Math.Exp(point.Y / K)) - Math.PI / 2.0) * 180.0 / Math.PI;
        }
    }
}
